package top.superflow.flow.runtime.mapper

import java.util.regex.Pattern

import top.superflow.exception.Exceptable
import top.superflow.flow.load.FlowPathUtils
import top.superflow.flow.runtime.flow.{FlowRequest, FlowResponse}
import top.superflow.flow.runtime.inf.{GeneralRequest, GeneralResponse}
import top.superflow.json.util.JsonUtils
import top.superflow.lang.StringUtils
import top.superflow.log.Loggable
import top.superflow.utils.regex.RegexUtils

import scala.collection.mutable

class MapperSyntaxParser private extends Exceptable with Loggable{


  def extractParameters(flowRequest : FlowRequest, flowResponse: FlowResponse, mappingRule : Map[String, String]) : Map[String, Any] = {
    val params = mutable.Map[String, Any]()

    if(flowRequest == null){
      throwRequestUnsupportedException("The flow request should not be null!")
    }

    if(flowResponse == null){
      throwRequestUnsupportedException("The flow response should not be null!")
    }

    val parsePatterns = MapperSyntaxParser.mapperSyntaxPatterns

    for((patternName, pattern) <- parsePatterns.getParsedPatterns){
      if(StringUtils.containsIgnoreCase(patternName, "request")){
        val paramsByRule = extractParamsFromRequest(flowRequest,patternName, pattern, mappingRule )
        params ++= paramsByRule
      }

      if(StringUtils.containsIgnoreCase(patternName, "response")){
        val paramsByRule = extractParamsFromResponse(flowResponse,patternName, pattern, mappingRule )
        params ++= paramsByRule
      }
    }

    params.toMap
  }


  private def extractParamsFromRequest(flowRequest: FlowRequest, patternName: String, pattern : String, mappingRule : Map[String, String]) : Map[String, Any] = {

    val params = mutable.Map[String, Any]()

    for((paramName, mappedParamRule) <- mappingRule){
      val parametersByRule = extractParamsFromRequest(flowRequest, patternName, pattern, paramName, mappedParamRule)
      params ++= parametersByRule
    }

    params.toMap
  }

  private def extractParamsFromResponse(flowResponse: FlowResponse, patternName: String, pattern : String, mappingRule : Map[String, String]) : Map[String, Any] = {

    val params = mutable.Map[String, Any]()

    for((paramName, mappedParamRule) <- mappingRule){
      val parametersByRule = extractParamsFromResponse(flowResponse, patternName, pattern, paramName, mappedParamRule)
      params ++= parametersByRule
    }

    params.toMap
  }

  private def extractParamsFromResponse(flowResponse: FlowResponse, patternName: String, pattern : String, paramName : String, mappedParamRule : String) : Map[String, Any] = {

    val validInnerConfigFilePattern = Pattern.compile(pattern);
    val matcher = validInnerConfigFilePattern.matcher(mappedParamRule);

    val params = mutable.Map[String, Any]()
    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_FLOW_RESPONSE) && matcher.find()){
      val paramsFromResponse = extractValueFromResponse(flowResponse, paramName, mappedParamRule)
      params ++= paramsFromResponse
    }

    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_PREVIOUS_RESPONSE) && matcher.find()){
      val paramsFromResponse = extractValueFromResponse(flowResponse.getLastResponse, paramName, mappedParamRule)
      params ++= paramsFromResponse
    }

    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_ROUTE_RESPONSE) && matcher.find()){
      val routeKey = extractRouteKey(mappedParamRule)
      val routeResponse = flowResponse.getResponse(routeKey)
      if(routeResponse.isEmpty){
        throwRequestUnsupportedException("There is no request under route {}", routeKey)
      }
      val paramsFromResponse = extractValueFromResponse(routeResponse.get, paramName, mappedParamRule)
      params ++= paramsFromResponse
    }

    params.toMap
  }

  private def extractParamsFromRequest(flowRequest: FlowRequest, patternName: String, pattern : String, paramName : String, mappedParamRule : String) : Map[String, Any] = {

    val validInnerConfigFilePattern = Pattern.compile(pattern);
    val matcher = validInnerConfigFilePattern.matcher(mappedParamRule);

    val params = mutable.Map[String, Any]()
    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_FLOW_REQUEST) && matcher.find()){
      val paramsFromRequest = extractValueFromRequest(flowRequest, paramName, mappedParamRule)
      params ++= paramsFromRequest
    }

    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_PREVIOUS_REQUEST) && matcher.find()){
      val paramsFromRequest = extractValueFromRequest(flowRequest.getLastRequest, paramName, mappedParamRule)
      params ++= paramsFromRequest
    }

    if(StringUtils.equalsIgnoreCase(patternName, MapperSyntaxParser.EXPECTED_RULE_ROUTE_REQUEST) && matcher.find()){
      val routeName = extractRouteKey(mappedParamRule)
      val routeRequest = flowRequest.getRouteRequest(routeName)
      if(routeRequest.isEmpty){
        throwRequestUnsupportedException("There is no request under route {}", routeName)
      }
      val paramsFromRequest = extractValueFromRequest(routeRequest.get, paramName, mappedParamRule)
      params ++= paramsFromRequest
    }

    params.toMap
  }

  private def extractValueFromRequest(generalRequest: GeneralRequest, paramName: String, mappedParamRule : String) : Map[String, Any] = {
    val splitter = MapperSyntaxParser.mapperSyntaxPatterns.getSplitter.getSign
    val paramMapPattern =  getPatternForParamQueue(splitter)

    val paramNames = RegexUtils.extractAllGroup(paramMapPattern, mappedParamRule)


    val paramNameQueue = new mutable.Queue[String]()

    paramNameQueue ++= paramNames

    val firstParamName = paramNameQueue.dequeue

    val valueForFistParam = generalRequest.getParameter(firstParamName)

    if(valueForFistParam.isEmpty){
      logger.info("There is no corresponding value for the parameter name {} in the request {}", firstParamName, generalRequest)
      return null
    }

    val mappedValue = ValueExtractUtils.extract(valueForFistParam.get, paramNameQueue)


    return Map[String,Any]((paramName, mappedValue))
  }

  private def extractValueFromResponse(generalResponse: GeneralResponse, paramName: String, mappedParamRule : String) : Map[String, Any] = {
    val splitter = MapperSyntaxParser.mapperSyntaxPatterns.getSplitter.getSign
    val paramMapPattern =  getPatternForParamQueue(splitter)

    val paramNames = RegexUtils.extractAllGroup(paramMapPattern, mappedParamRule)


    val paramNameQueue = new mutable.Queue[String]()

    paramNameQueue ++= paramNames

    val mappedValue = ValueExtractUtils.extract(generalResponse.getResponse, paramNameQueue)


    return Map[String,Any]((paramName, mappedValue))
  }

  private def getPatternForParamQueue(splitter: String) = {
    "(?:" + RegexUtils.escape(splitter) + ")(\\w+)"
  }

  private def extractRouteKey(mappedParamRule : String) : String = {
    val routePatternRegex = getPatternToExtractRouteName

    val extractedRouteName = RegexUtils.extractValue(routePatternRegex, mappedParamRule, 2)
    if(extractedRouteName == null){
      throwRequestUnsupportedException("There is no corresponding pattern in {} for {}", mappedParamRule, routePatternRegex)
    }

    return extractedRouteName
  }

  private def getPatternToExtractRouteName = {
    "(\\w+\\()(\\w+)(\\).*)"
  }
}

object MapperSyntaxParser extends Exceptable{

  private val instance = new MapperSyntaxParser

  private val mapperSyntaxRulesFile = FlowPathUtils.getResourcePath("mapper/expression/MapperSyntaxPatterns.json")

  private final val EXPECTED_RULE_FLOW_REQUEST = "flowRequest"

  private final val EXPECTED_RULE_FLOW_RESPONSE = "flowResponse"

  private final val EXPECTED_RULE_PREVIOUS_REQUEST = "previousRequest"

  private final val EXPECTED_RULE_PREVIOUS_RESPONSE = "previousResponse"

  private final val EXPECTED_RULE_ROUTE_REQUEST = "routeRequest"

  private final val EXPECTED_RULE_ROUTE_RESPONSE = "routeResponse"

  private val expectedRuleNames = Set(EXPECTED_RULE_FLOW_REQUEST,
                                    EXPECTED_RULE_FLOW_RESPONSE,
                                    EXPECTED_RULE_PREVIOUS_REQUEST,
                                    EXPECTED_RULE_PREVIOUS_RESPONSE,
                                    EXPECTED_RULE_ROUTE_REQUEST,
                                    EXPECTED_RULE_ROUTE_RESPONSE)


  private val mapperSyntaxPatterns = JsonUtils.readJson[MapperSyntaxPatterns](mapperSyntaxRulesFile, classOf[MapperSyntaxPatterns])

  validateMapperSyntaxRules(mapperSyntaxPatterns)

  mapperSyntaxPatterns.init

  def getInstance = instance


  private def validateMapperSyntaxRules(mapperSyntaxRules : MapperSyntaxPatterns): Unit ={
    if(mapperSyntaxRules == null){
      throwRequestUnsupportedException("There is no mapper syntax rules configured under {}" + mapperSyntaxRulesFile)
    }

    validateIntegrality(mapperSyntaxRules)

    validateAliasConflicts(mapperSyntaxRules)

  }

  private def validateIntegrality(mapperSyntaxRules : MapperSyntaxPatterns): Unit ={
    if(mapperSyntaxRules.getPatterns == null){
      throwRequestUnsupportedException("The rule should not be null!")
    }

    for(ruleName <- expectedRuleNames){
      if(!mapperSyntaxRules.getPatterns.contains(ruleName)){
        throwRequestUnsupportedException("The expected rule {} doesn't exist!" , ruleName)
      }
    }
  }

  private def validateAliasConflicts(mapperSyntaxRules : MapperSyntaxPatterns): Unit ={
    val existingAliases = mutable.Map[String, mutable.Set[String]]()
    for((ruleName, rule) <- mapperSyntaxRules.getPatterns){
      validateAlias(existingAliases, rule.getAlias, ruleName)
      addAlias(existingAliases, rule.getAlias, ruleName)
    }

  }

  private def validateAlias(existingAliases : mutable.Map[String, mutable.Set[String]], newAlias : Array[String], aliasHolder : String): Unit ={
    for((existingRuleName, existingAliases) <- existingAliases){
      for(alias <- newAlias){
        if(existingAliases.contains(alias)){
          throwRequestUnsupportedException("The alias {} has been already occupied by rule {}", alias, existingRuleName)
        }
      }
    }
  }

  private def addAlias(existingAliases : mutable.Map[String, mutable.Set[String]], newAlias : Array[String], aliasHolder : String): Unit ={
    val aliasSet =  mutable.Set[String]()
    aliasSet ++= newAlias.toSet
    existingAliases += (aliasHolder -> aliasSet)
  }

  def main(args: Array[String]): Unit = {
//    getInstance.test("abc(adfadfa122).baccc.iiiiii.fffff")

//    print(RegexUtils.escape("?."))
  }
}
